package jmine.tec.report.script;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedList;
import java.util.List;

import jmine.tec.report.api.Report;
import jmine.tec.report.api.batch.ReportCreator;
import jmine.tec.report.export.ReportFileExporter;
import jmine.tec.script.ScriptExecutorException;
import jmine.tec.script.ScriptLogger;
import jmine.tec.script.persistent.domain.Script;
import jmine.tec.utils.Tuple;
import jmine.tec.utils.UnmodifiableTuple;
import bancosys.tec.persist.controller.PersistenceEnabledController;
import bancosys.tec.persist.dao.BaseDAO;
import bancosys.tec.persist.dao.BeanNotFoundException;

/**
 * Implementação de ReportCreator que delega a criação do report para um script.
 * 
 * @author lundberg
 * @param <P> tipo de parâmetro
 */
public class ScriptReportCreator<P> implements ReportCreator<P> {

    private ReportFileExporter exporter;

    private PersistenceEnabledController controller;

    private Class<P> parameterClass;

    private String mnemonicoScript;

    private String creatorKey;

    private ScriptLogger lastLogger;
    
    /**
     * Construtor
     */
    public ScriptReportCreator() {
    }

    /**
     * {@inheritDoc}
     */
    public List<Tuple<String, InputStream>> createReportFiles(P parameter) throws IOException {
        List<Tuple<String, InputStream>> result = new LinkedList<Tuple<String, InputStream>>();
        try {
            for (Tuple<String, Report> report : this.doCreateReports(parameter)) {
                result.add(new UnmodifiableTuple<String, InputStream>(report.head(), this.getInputStream(report.tail())));
            }
        } catch (BeanNotFoundException e) {
            throw new IllegalArgumentException("Script de criação de relatório não encontrado.", e);
        } catch (ScriptExecutorException e) {
            throw new IllegalStateException("Erro inesperado executando script de criação de relatório", e);
        }
        return result;
    }

    /**
     * {@inheritDoc}
     */
    public List<Report> createReports(P parameter) {
        List<Report> result = new LinkedList<Report>();
        try {
            for (Tuple<String, Report> report : this.doCreateReports(parameter)) {
                result.add(report.tail());
            }
        } catch (BeanNotFoundException e) {
            throw new IllegalArgumentException("Script de criação de relatório não encontrado.", e);
        } catch (ScriptExecutorException e) {
            throw new IllegalStateException("Erro inesperado executando script de criação de relatório", e);
        }
        return result;
    }
    
    /**
     * 
     * @return o script logger associado à ultima execução do createReports ou createReportFiles
     */
    public ScriptLogger getScriptLogger() {
        return this.lastLogger;
    }

    /**
     * Cria uma nova instância do parâmetro recebido pelo relatório.
     * 
     * @return parâmetro
     */
    public P createParameter() {
        try {
            Constructor<P> constructor = this.parameterClass.getDeclaredConstructor(new Class[0]);
            return constructor.newInstance(new Object[0]);
        } catch (IllegalArgumentException e) {
            throw new IllegalStateException("Falha instanciando parâmetro de criação de relatório.", e);
        } catch (InstantiationException e) {
            throw new IllegalStateException("Falha instanciando parâmetro de criação de relatório.", e);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Falha instanciando parâmetro de criação de relatório.", e);
        } catch (InvocationTargetException e) {
            throw new IllegalStateException("Falha instanciando parâmetro de criação de relatório.", e);
        } catch (SecurityException e) {
            throw new IllegalStateException("Falha instanciando parâmetro de criação de relatório.", e);
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException("Falha instanciando parâmetro de criação de relatório.", e);
        }
    }

    /**
     * Cria o arquivo referente ao report e devolve um inputstream capaz de ler o resultado.
     * 
     * @param report report
     * @return InputStream
     * @throws IOException IOException
     */
    private InputStream getInputStream(Report report) throws IOException {
        File tmp = File.createTempFile("pegasus", "report");
        OutputStream stream = new FileOutputStream(tmp);
        this.exporter.export(stream, report);
        stream.flush();
        stream.close();
        return new FileInputStream(tmp);
    }

    /**
     * Cria os relatórios apropriados dado o parâmetro
     * 
     * @param parameter parameter
     * @return lista de tuplas de nome do arquivo para o relatório
     * @throws BeanNotFoundException BeanNotFoundException
     * @throws ScriptExecutorException ScriptExecutorException
     */
    private List<Tuple<String, Report>> doCreateReports(P parameter) throws BeanNotFoundException, ScriptExecutorException {
        BaseDAO<Script> dao = this.getController().getDAOFactory().getGenericDAO(Script.class);
        Script script = dao.findByNaturalKey(this.mnemonicoScript);
        ReportScriptResult result = script.createContext(CreateReportScriptExecutionContext.class).prepare(parameter).dispatchExecution();
        this.lastLogger = result.getScriptLogger();
        return result.getReports();
    }
    

    /**
     * @return the exporter
     */
    public ReportFileExporter getExporter() {
        return this.exporter;
    }

    /**
     * @param exporter the exporter to set
     */
    public void setExporter(ReportFileExporter exporter) {
        this.exporter = exporter;
    }

    /**
     * @return the controller
     */
    public PersistenceEnabledController getController() {
        return this.controller;
    }

    /**
     * @param controller the controller to set
     */
    public void setController(PersistenceEnabledController controller) {
        this.controller = controller;
    }

    /**
     * {@inheritDoc}
     */
    public int compareTo(ReportCreator<?> o) {
        return this.getCreatorKey().compareTo(o.getCreatorKey());
    }

    /**
     * {@inheritDoc}
     */
    public String getCreatorKey() {
        return this.creatorKey;
    }

    /**
     * @return the parameterClass
     */
    public Class<P> getParameterClass() {
        return this.parameterClass;
    }

    /**
     * @return the mnemonicoScript
     */
    public String getMnemonicoScript() {
        return this.mnemonicoScript;
    }

    /**
     * @param parameterClass the parameterClass to set
     */
    public void setParameterClass(Class<P> parameterClass) {
        this.parameterClass = parameterClass;
    }

    /**
     * @param mnemonicoScript the mnemonicoScript to set
     */
    public void setMnemonicoScript(String mnemonicoScript) {
        this.mnemonicoScript = mnemonicoScript;
    }

    /**
     * @param creatorKey the creatorKey to set
     */
    public void setCreatorKey(String creatorKey) {
        this.creatorKey = creatorKey;
    }
}
